use core::ptr::null_mut;

use crate::alloc_::*;
use crate::heap::*;
use crate::internal::*;
use crate::types::*;
use crate::types_extra::*;

pub unsafe fn mi_heap_malloc_zero_aligned_at(
    heap: *mut mi_heap_t,
    size: usize,
    alignment: usize,
    offset: usize,
    zero: bool,
) -> *mut u8 {
    assert!(alignment > 0);

    if alignment <= size_of::<*const ()>() {
        return _mi_heap_malloc_zero(heap, size, zero);
    }

    if size >= SIZE_MAX as usize - alignment {
        return null_mut();
    }

    if size <= MI_SMALL_SIZE_MAX as usize {
        let page = _mi_heap_get_free_small_page(heap, size);
        if !(&mut *page).free.is_null() && ((&mut *page).free as usize + offset) % alignment == 0 {
            mi_heap_stat_increase!(heap, malloc, size);
        }
        let p = _mi_page_malloc(heap, page, size);
        if zero {
            libc::memset(p as _, 0, size);
        }
        return p;
    }

    let p = _mi_heap_malloc_zero(heap, size + alignment - 1, zero);
    if p.is_null() {
        return null_mut();
    }

    (&mut *_mi_ptr_page(p)).flags.__bindgen_anon_1.has_aligned = true;
    let adjust = alignment - ((p as usize + offset) % alignment);
    let aligned_p = if adjust == alignment {
        p
    } else {
        p.add(adjust)
    };

    aligned_p
}

pub unsafe fn _mi_heap_malloc_zero(heap: *mut mi_heap_t, size: usize, zero: bool) -> *mut u8 {
    let p = mi_heap_malloc(heap, size);
    if zero && !p.is_null() {
        libc::memset(p as _, 0, size);
    }
    p
}

pub unsafe fn mi_malloc_zero_aligned_at(
    size: usize,
    alignment: usize,
    offset: usize,
    zero: bool,
) -> *mut u8 {
    mi_heap_malloc_zero_aligned_at(mi_get_default_heap(), size, alignment, offset, zero)
}

pub unsafe fn mi_malloc_aligned_at(size: usize, alignment: usize, offset: usize) -> *mut u8 {
    mi_malloc_zero_aligned_at(size, alignment, offset, false)
}

pub unsafe fn mi_malloc_aligned(size: usize, alignment: usize) -> *mut u8 {
    mi_malloc_aligned_at(size, alignment, 0)
}

pub unsafe fn mi_zalloc_aligned_at(size: usize, alignment: usize, offset: usize) -> *mut u8 {
    mi_malloc_zero_aligned_at(size, alignment, offset, true)
}

pub unsafe fn mi_zalloc_aligned(size: usize, alignment: usize) -> *mut u8 {
    mi_zalloc_aligned_at(size, alignment, 0)
}

pub unsafe fn mi_realloc_zero_aligned_at(
    p: *mut u8,
    newsize: usize,
    alignment: usize,
    offset: usize,
    zero: bool,
) -> *mut u8 {
    if alignment <= size_of::<*mut ()>() {
        return _mi_realloc_zero(p, newsize, zero);
    }

    if p.is_null() {
        return mi_malloc_zero_aligned_at(newsize, alignment, offset, zero);
    }

    let size = mi_usable_size(p);
    if newsize <= size && newsize >= (size - (size / 2)) && (p as usize + offset) % alignment == 0 {
        return p;
    } else {
        let newp = mi_malloc_aligned_at(newsize, alignment, offset);
        if !newp.is_null() {
            if zero && newsize > size {
                let start = if size > size_of::<*mut ()>() {
                    size - size_of::<*mut ()>()
                } else {
                    0
                };
                libc::memset(newp.add(start) as _, 0, newsize - start);
            }
            libc::memcpy(newp as _, p as _, size.min(newsize));
        }
        newp
    }
}

pub unsafe fn _mi_realloc_aligned(
    p: *mut u8,
    newsize: usize,
    alignment: usize,
    zero: bool,
) -> *mut u8 {
    if alignment <= size_of::<*mut ()>() {
        return _mi_realloc_zero(p, newsize, zero);
    }
    let offset = p as usize % alignment;
    mi_realloc_zero_aligned_at(p, newsize, alignment, offset, zero)
}

#[allow(dead_code)]
pub unsafe fn mi_realloc_aligned_at(
    p: *mut u8,
    newsize: usize,
    alignment: usize,
    offset: usize,
) -> *mut u8 {
    mi_realloc_zero_aligned_at(p, newsize, alignment, offset, false)
}

pub unsafe fn mi_realloc_aligned(p: *mut u8, newsize: usize, alignment: usize) -> *mut u8 {
    _mi_realloc_aligned(p, newsize, alignment, false)
}
